﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;

namespace LeafHttp
{
    /// <summary>
    /// Http请求
    /// </summary>
    public class HttpRequest : IHttpRequest
    {
        HttpClient _client;
        RequestConfig _config;

        /// <summary>
        /// Http客户端
        /// </summary>
        public HttpClient HttpClient { get { return _client; } }
        /// <summary>
        /// 请求参数
        /// </summary>
        public RequestConfig RequestConfig { get { return _config; } }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="client">Http客户端</param>
        /// <param name="config">请求参数</param>
        public HttpRequest(HttpClient client, RequestConfig config)
        {
            _client = client;
            _config = config;
        }

        /// <summary>
        /// 创建参数（Url参数或Form参数，前面不带?）
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private string CreateParams(Dictionary<string, object> data)
        {
            if (data == null)
                return string.Empty;

            StringBuilder sbParams = new StringBuilder();
            int i = 0;
            foreach (var item in data)
            {
                if (i > 0)
                {
                    sbParams.Append("&");
                }

                sbParams.Append(item.Key + "=" + (item.Value != null ? Uri.EscapeDataString(item.Value.ToString()) : string.Empty));
                i++;
            }

            return sbParams.ToString();
        }

        /// <summary>
        /// 创建用于文件上传的表单数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <param name="postData"></param>
        /// <returns></returns>
        private void CreateMultipartFormData(Dictionary<string, object> data, out string contentType, out byte[] postData)
        {
            List<byte> byteList = new List<byte>();
            string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
            byte[] boundaryEndBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
            string itemParamTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
            string itemFileTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
            foreach (var item in data)
            {
                if (item.Value is FormFile)
                {
                    // 文件
                    FormFile formFile = item.Value as FormFile;
                    if (formFile != null)
                    {
                        byteList.AddRange(boundaryBytes);
                        string itemStr = string.Format(itemFileTemplate, item.Key, formFile.FileName);
                        byteList.AddRange(Encoding.UTF8.GetBytes(itemStr));
                        byteList.AddRange(formFile.FileData);
                    }
                }
                else
                {
                    // 参数
                    byteList.AddRange(boundaryBytes);
                    string itemStr = string.Format(itemParamTemplate, item.Key, item.Value);
                    byteList.AddRange(Encoding.UTF8.GetBytes(itemStr));
                }
            }
            byteList.AddRange(boundaryEndBytes);
            contentType = HttpContentType.Multipart_form_data + ";boundary=" + boundary;
            postData = byteList.ToArray();
        }

        /// <summary>
        /// 设置请求头的值，如果存在则自动替换
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void SetHeaderValueReplace(ref Dictionary<string, string> headers, string key, string value)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException(nameof(value));

            if (headers == null)
            {
                headers = new Dictionary<string, string>();
            }

            if (!headers.ContainsKey(key))
            {
                headers.Add(key, value);
            }
            else
            {
                headers[key] = value;
            }
        }

        /// <summary>
        /// 设置多个请求头的值，如果存在则自动替换
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="dicValues"></param>
        private void SetMultiHeaderValuesReplace(ref Dictionary<string, string> headers, Dictionary<string, string> dicValues)
        {
            if (dicValues == null)
                return;

            foreach (var item in dicValues)
            {
                SetHeaderValueReplace(ref headers, item.Key, item.Value);
            }
        }

        /// <summary>
        /// 设置请求内容类型，如果已设置则不替换
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        private void SetContentTypeIfUnset(ref Dictionary<string, string> headers, string contentType)
        {
            if (headers == null)
            {
                headers = new Dictionary<string, string>();
            }

            if (!headers.ContainsKey(HttpHeader.ContentType))
            {
                headers.Add(HttpHeader.ContentType, contentType);
            }
        }

        /// <summary>
        /// 添加请求标头
        /// </summary>
        /// <param name="request"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void AddRequestHeader(ref HttpWebRequest request, string key, string value)
        {
            string esValue = Uri.EscapeUriString(value);
            // 由于Content-Type等特殊标头只能使用属性来设置，为了统一在标头中设置，使用反射调用无验证的添加标头方法
            MethodInfo method = request.Headers.GetType().GetMethod("AddWithoutValidate", BindingFlags.Instance | BindingFlags.NonPublic);
            method.Invoke(request.Headers, new[] { key, esValue });
        }

        /// <summary>
        /// 添加多个请求头
        /// </summary>
        /// <param name="request"></param>
        /// <param name="dicHeaders"></param>
        private void AddMultiRequestHeaders(ref HttpWebRequest request, Dictionary<string, string> dicHeaders)
        {
            if (dicHeaders == null)
                return;

            foreach (var item in dicHeaders)
            {
                AddRequestHeader(ref request, item.Key, item.Value);
            }
        }

        /// <summary>
        /// 添加请求的Cookie
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cookie"></param>
        private void AddRequestCookie(ref HttpWebRequest request, HttpCookie cookie)
        {
            if (request.CookieContainer == null)
            {
                request.CookieContainer = new CookieContainer();
            }

            var cookieRaw = new Cookie(cookie.Name, Uri.EscapeDataString(cookie.Value), cookie.Path);
            if (!string.IsNullOrEmpty(cookie.Domain))
            {
                cookieRaw.Domain = cookie.Domain;
            }
            else
            {
                cookieRaw.Domain = request.RequestUri.Host;
            }

            request.CookieContainer.Add(cookieRaw);
        }

        /// <summary>
        /// 添加多个请求的Cookie
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cookies"></param>
        private void AddMultiRequestCookies(ref HttpWebRequest request, List<HttpCookie> cookies)
        {
            if (cookies == null)
                return;

            foreach (var cookie in cookies)
            {
                AddRequestCookie(ref request, cookie);
            }
        }

        /// <summary>
        /// 创建Http请求
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private HttpWebRequest CreateRequest(RequestConfig config)
        {
            if (string.IsNullOrEmpty(config.Url))
            {
                throw new ArgumentNullException("必须提供config.Url");
            }

            var url = config.Url;
            if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
            {
                url = (!string.IsNullOrEmpty(_client.BaseUrl) ? _client.BaseUrl : string.Empty) + config.Url;
            }

            if (config.Params != null)
            {
                string urlParams = CreateParams(config.Params);
                if (!string.IsNullOrEmpty(urlParams))
                {
                    url += "?" + urlParams;
                }
            }

            var method = !string.IsNullOrEmpty(config.Method) ? config.Method : HttpMethod.Get;
            var headers = new Dictionary<string, string>();            
            SetMultiHeaderValuesReplace(ref headers, _client.DefaultHeadersCommon);
            if (method.ToLower() == HttpMethod.Post.ToLower() ||
                method.ToLower() == HttpMethod.Put.ToLower() ||
                method.ToLower() == HttpMethod.Patch.ToLower())
            {
                SetMultiHeaderValuesReplace(ref headers, _client.DefaultHeadersPost);
            }

            SetMultiHeaderValuesReplace(ref headers, config.Headers);

            byte[] contentData = null;
            if (config.Data != null &&
                (method.ToLower() == HttpMethod.Post.ToLower() ||
                method.ToLower() == HttpMethod.Put.ToLower() ||
                method.ToLower() == HttpMethod.Patch.ToLower()))
            {
                if (config.UseMultipartFormDataPost)
                {
                    // 上传文件
                    if (!(config.Data is Dictionary<string, object>))
                    {
                        throw new Exception("config.Data格式不符合config.UseMultipartFormDataPost要求，应为Dictionary<string, object>");
                    }

                    string contentType;
                    CreateMultipartFormData((config.Data as Dictionary<string, object>), out contentType, out contentData);
                    SetContentTypeIfUnset(ref headers, contentType);
                }
                else
                {
                    string data;
                    if (config.Data is Dictionary<string, object>)
                    {
                        data = CreateParams(config.Data as Dictionary<string, object>);
                        SetContentTypeIfUnset(ref headers, HttpContentType.Application_x_www_form_urlencoded);
                    }
                    else
                    {
                        data = config.Data.ToString();
                    }

                    if (!string.IsNullOrEmpty(data))
                    {
                        contentData = Encoding.UTF8.GetBytes(data);
                    }
                }
            }

            SetContentTypeIfUnset(ref headers, HttpContentType.Application_json);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method;
            AddMultiRequestHeaders(ref request, headers);
            AddMultiRequestCookies(ref request, _client.DefaultCookies);
            AddMultiRequestCookies(ref request, config.Cookies);
            if (contentData != null)
            {
                request.ContentLength = contentData.Length;
                using (Stream stream = request.GetRequestStream())
                {
                    stream.Write(contentData, 0, contentData.Length);
                }
            }

            if (config.Timeout > 0)
            {
                request.Timeout = config.Timeout;
            }
            else
            {
                request.Timeout = _client.DefaultTimeout;
            }

            return request;
        }

        /// <summary>
        /// 获取响应结果
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        private ResponseResult GetResponseResult(HttpWebRequest request, HttpWebResponse response)
        {
            ResponseResult result = new ResponseResult();
            Stream stream = response.GetResponseStream();
            switch (_config.ResponseDataType)
            {
                case ResponseDataType.Stream:
                    result.Data = stream;
                    break;
                case ResponseDataType.ArrayBuffer:
                    List<byte> byteList = new List<byte>();
                    byte[] buffer = new byte[1024];
                    int readLen;
                    while ((readLen = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        for (int i = 0; i < readLen; i++)
                        {
                            byteList.Add(buffer[i]);
                        }
                    }
                    stream.Close();
                    result.Data = byteList.ToArray();
                    break;
                default:
                case ResponseDataType.Text:
                    using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                    {
                        result.Data = reader.ReadToEnd();
                        stream.Close();
                    }
                    break;
            }
            result.Status = (int)response.StatusCode;
            result.StatusText = response.StatusDescription;
            result.Headers = new Dictionary<string, string>();
            foreach (var key in response.Headers.AllKeys)
            {
                result.Headers.Add(key, response.Headers[key]);
            }
            result.Config = _config;
            result.RequestRaw = request;
            result.ResponseRaw = response;
            return result;
        }

        /// <summary>
        /// 执行拦截器方法
        /// </summary>
        /// <param name="action"></param>
        private void ExecuteInterceptors(Action<IRequestInterceptor> action)
        {
            foreach (IRequestInterceptor interceptor in _client.Interceptors)
            {
                action(interceptor);
            }
        }

        /// <summary>
        /// 执行请求
        /// </summary>
        /// <returns></returns>
        public ExecuteResult Execute()
        {
            ExecuteResult rst = new ExecuteResult();
            HttpWebRequest request = null;

            try
            {
                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.Request(_config);
                });

                request = CreateRequest(_config);
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    var resResult = GetResponseResult(request, response);
                    ExecuteInterceptors((interceptor) =>
                    {
                        interceptor.Response(resResult);
                    });
                    rst.IsSuccess = true;
                    rst.Message = string.Empty;
                    rst.Response = resResult;
                    return rst;
                }
            }
            catch (WebException ex)
            {
                var error = new ErrorResult()
                {
                    Message = ex.Message,
                    Response = ex.Response != null ? GetResponseResult(request, (HttpWebResponse)ex.Response) : null
                };

                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.ResponseError(error);
                });

                rst.IsSuccess = false;
                rst.Message = error.Message;
                rst.Response = error.Response;
                return rst;
            }
            catch (Exception ex)
            {
                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.RequestError(ex);
                });

                rst.IsSuccess = false;
                rst.Message = ex.Message;
                rst.Response = null;
                return rst;
            }
        }

        /// <summary>
        /// 异步执行请求
        /// </summary>
        /// <param name="responseFunc">响应回调方法</param>
        /// <param name="errorFunc">出错回调方法</param>
        public void ExecuteAsync(Action<ResponseResult> responseFunc, Action<ErrorResult> errorFunc = null)
        {
            try
            {
                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.Request(_config);
                });

                HttpWebRequest request = CreateRequest(_config);
                object[] resultState = new object[]
                {
                    request,
                    responseFunc,
                    errorFunc
                };
                request.BeginGetResponse(new AsyncCallback(ResponseCallback), resultState);
            }
            catch (Exception ex)
            {
                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.RequestError(ex);
                });

                if (errorFunc != null)
                {
                    ErrorResult errResult = new ErrorResult()
                    {
                        Message = ex.Message,
                    };
                    errorFunc(errResult);
                }
                else
                {
                    throw ex;
                }
            }
        }

        /// <summary>
        /// 异步请求的回调函数
        /// </summary>
        /// <param name="result"></param>
        private void ResponseCallback(IAsyncResult result)
        {
            object[] resultState = result.AsyncState as object[];
            HttpWebRequest request = resultState[0] as HttpWebRequest;
            Action<ResponseResult> responseFunc = resultState[1] as Action<ResponseResult>;
            Action<ErrorResult> errorFunc = resultState[2] as Action<ErrorResult>;
            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result))
                {
                    ResponseResult resResult = GetResponseResult(request, response);
                    ExecuteInterceptors((interceptor) =>
                    {
                        interceptor.Response(resResult);
                    });
                    responseFunc(resResult);
                }
            }
            catch (WebException ex)
            {
                ErrorResult error = new ErrorResult()
                {
                    Message = ex.Message,
                    Response = ex.Response != null ? GetResponseResult(request, (HttpWebResponse)ex.Response) : null
                };

                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.ResponseError(error);
                });

                if (errorFunc != null)
                {
                    errorFunc(error);
                }
                else
                {
                    throw ex;
                }
            }
            catch (Exception ex)
            {
                ErrorResult error = new ErrorResult()
                {
                    Message = ex.Message
                };

                ExecuteInterceptors((interceptor) =>
                {
                    interceptor.ResponseError(error);
                });

                if (errorFunc != null)
                {
                    errorFunc(error);
                }
                else
                {
                    throw ex;
                }
            }
        }
    }
}
